Skip to content

淺談 Flag Enum 的應用與心得

TLDR

  • Flag Enum 透過 [Flags] 屬性與位元運算,能有效管理多重狀態或選項組合。
  • 定義時應使用 2 的 N 次方數值(如 1, 2, 4, 8),避免使用 ~ (NOT) 運算符定義複合值,以防 ToString() 輸出異常或邏輯判斷錯誤。
  • 判斷狀態建議優先使用 HasFlag 方法,其語意比位元運算更清晰。
  • 避免定義 All 類型的列舉值,因為當未來擴充新項目時,All 往往不會自動更新,容易造成邏輯漏洞。
  • Microsoft 建議 Flag Enum 應使用複數命名(如 RegexOptions)。

Enum 的基本概念與優勢

Enum(列舉型別)是一種由整數常數組成的實值型別,預設為 int。使用 Enum 可以為數值賦予具體含義,提升程式碼可讀性,並限制輸入範圍以增加穩定性。

csharp
enum Action : ushort {
    None = 0,
    Query = 1,
    Create = 10,
    Update = 11,
    Dalete = 12
}

Flag Enum 的定義與應用

Flag Enum 允許透過位元運算組合多個狀態。定義時需加上 [Flags] 屬性,並以 2 的 N 次方定義各項值。

定義方式

建議使用位元移位(Bit shifting)來定義,確保數值不重複且符合二進位邏輯:

csharp
[Flags]
enum Permissions {
    None = 0,
    CanQuery = 1 << 0,  // 1
    CanCreate = 1 << 1, // 2
    CanUpdate = 1 << 2, // 4
    CanDelete = 1 << 3  // 8
}

簡化方法參數

當方法需要接收多個布林狀態時,使用 Flag Enum 可大幅簡化參數列表:

csharp
// 改寫前
void Execute(bool canQuery, bool canCreate, bool canUpdate, bool canDelete) { }

// 改寫後
void Execute(Permissions permiss) { }

位元運算與狀態判斷

Flag Enum 的核心在於位元運算,可將其視為集合操作:

  • OR (|):聯集,用於組合多個權限。
  • AND (&):交集,用於檢查是否包含特定權限。
  • XOR (^):對稱差集,用於切換狀態。
  • NOT (~):補集,用於排除特定權限。

移除特定項目的技巧

由於位元運算沒有直接的「差集」運算子,若要移除特定項目,可採用以下方式:

  • Permissions.CanUpsert & ~Permissions.CanCreate
  • (Permissions.CanUpsert | Permissions.CanCreate) ^ Permissions.CanCreate

判斷是否包含特定值

建議使用 .HasFlag() 方法,該方法在 .NET Framework 4.0 引入,語意明確且易於閱讀:

csharp
bool hasCreate = Permissions.CanUpsert.HasFlag(Permissions.CanCreate);

常見陷阱與最佳實踐

避免使用 NOT (~) 運算符定義複合值

什麼情況下會遇到這個問題:當開發者嘗試使用 ~ 定義「除了某項以外的所有權限」時。 原因分析:使用 ~ 定義的複合值在 ToString() 時可能無法正確解析為名稱,而是直接顯示數值。此外,這類定義會包含未定義的位元,導致 HasFlag 的判斷結果不如預期。

謹慎使用 All 命名

什麼情況下會遇到這個問題:在 Enum 中定義一個名為 All 的項目來代表所有權限。 原因分析:All 僅包含定義當下的項目。若未來新增了 CanExport 項目,All 的值並不會自動更新,導致邏輯錯誤。建議避免定義此類全域變數。

None 的判斷邏輯

什麼情況下會遇到這個問題:使用 HasFlag(None) 進行判斷時。 原因分析:從位元運算角度看,None (0) 是任何集合的子集,因此 AnyFlag.HasFlag(None) 永遠會回傳 true。這是符合數學邏輯的正確行為,開發時需特別留意此特性。

flag enum none check correct

異動歷程

    • 初版文件建立。